#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct TexCoordsAccessor {
        VertexBuffer* vertex_buffer;
    };
    VALUE rb_cVertexBuffer_TexCoordsAccessor;
}

void ruby_VertexBuffer_mark (VertexBuffer* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_VertexBuffer_free (VertexBuffer* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~VertexBuffer ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_VertexBuffer_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(VertexBuffer));
    return Data_Wrap_Struct (self, ruby_VertexBuffer_mark, ruby_VertexBuffer_free, p);
}

VALUE ruby_VertexBuffer_initialize (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    __TRY__;
    new (p) VertexBuffer;
    __CATCH__;
    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_VertexBuffer_get_colors (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    float        scale_bias[4] = {0,0,0,0};
    VertexArray* colors;
    __TRY__;
    colors = p->getColors (scale_bias);
    __CATCH__;
    VALUE val_colors = colors ? (VALUE)colors->getExportedEntity() : Qnil;
    VALUE val_scale  = rb_float_new(scale_bias[0]);
    VALUE val_bias   = rb_ary_new2 (3);
    rb_ary_push (val_bias, rb_float_new(scale_bias[1]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[2]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[3]));
    VALUE val_ret = rb_ary_new2 (3);
    rb_ary_push (val_ret, val_colors);
    rb_ary_push (val_ret, val_scale);
    rb_ary_push (val_ret, val_bias);
    return val_ret;
}

VALUE ruby_VertexBuffer_get_default_color (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    unsigned int argb;
    __TRY__;
    argb = p->getDefaultColor ();
    __CATCH__;
    return UINT2NUM(argb);
}

VALUE ruby_VertexBuffer_get_normals (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    float        scale_bias[4] = {0,0,0,0};
    VertexArray* normals;
    __TRY__;
    normals = p->getNormals (scale_bias);
    __CATCH__;
    VALUE val_normals = normals ? (VALUE)normals->getExportedEntity() : Qnil;
    VALUE val_scale   = rb_float_new(scale_bias[0]);
    VALUE val_bias    = rb_ary_new2 (3);
    rb_ary_push (val_bias, rb_float_new(scale_bias[1]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[2]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[3]));
    VALUE val_ret = rb_ary_new2 (3);
    rb_ary_push (val_ret, val_normals);
    rb_ary_push (val_ret, val_scale);
    rb_ary_push (val_ret, val_bias);
    return val_ret;
}

VALUE ruby_VertexBuffer_get_positions (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    float        scale_bias[4] = {0,0,0,0};
    VertexArray* positions;
    __TRY__;
    positions = p->getPositions (scale_bias);
    __CATCH__;
    VALUE val_positions = positions ? (VALUE)positions->getExportedEntity() : Qnil;
    VALUE val_scale     = rb_float_new(scale_bias[0]);
    VALUE val_bias      = rb_ary_new2 (3);
    rb_ary_push (val_bias, rb_float_new(scale_bias[1]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[2]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[3]));
    VALUE val_ret = rb_ary_new2 (3);
    rb_ary_push (val_ret, val_positions);
    rb_ary_push (val_ret, val_scale);
    rb_ary_push (val_ret, val_bias);
    return val_ret;
}

VALUE ruby_VertexBuffer_get_tex_coords (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    TexCoordsAccessor* accessor;
    VALUE          val_accessor = Data_Make_Struct (rb_cVertexBuffer_TexCoordsAccessor, TexCoordsAccessor, 0, -1, accessor);
    accessor->vertex_buffer = p;
    return val_accessor;
}

VALUE ruby_VertexBuffer_get_vertex_count (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    int vertex_count;
    __TRY__;
    vertex_count = p->getVertexCount ();
    __CATCH__;
    return INT2NUM(vertex_count);
}

VALUE ruby_VertexBuffer_set_colors (VALUE self, VALUE val_colors)
{
    if (!rb_obj_is_kind_of (val_colors, rb_cVertexArray)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexArray)");
    }
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    VertexArray* colors;
    Data_Get_Struct (val_colors, VertexArray, colors);
    __TRY__;
    p->setColors (colors);
    __CATCH__;
    return Qnil;
}

VALUE ruby_VertexBuffer_set_default_color (VALUE self, VALUE val_argb)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    int argb = NUM2UINT (val_argb);
    __TRY__;
    p->setDefaultColor (argb);
    __CATCH__;
    return Qnil;
}

VALUE ruby_VertexBuffer_set_normals (VALUE self, VALUE val_normals)
{
    if (!rb_obj_is_kind_of (val_normals, rb_cVertexArray)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexArray)");
    }
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    VertexArray* normals;
    Data_Get_Struct (val_normals, VertexArray, normals);
    __TRY__;
    p->setNormals (normals);
    __CATCH__;
    return Qnil;
}

VALUE ruby_VertexBuffer_set_positions (VALUE self, VALUE val_args)
{
    rb_check_type (val_args, T_ARRAY);
    if (RARRAY_LEN(val_args) != 3) {
        rb_raise (rb_eArgError, "wrong array length %d (expected 3)", RARRAY_LEN(val_args));
    }
    VALUE val_positions = rb_ary_entry (val_args, 0);
    VALUE val_scale     = rb_ary_entry (val_args, 1);
    VALUE val_bias      = rb_ary_entry (val_args, 2);

    if (!rb_obj_is_kind_of (val_positions, rb_cVertexArray)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexArray)");
    }
    if (RARRAY_LEN(val_bias) < 3) {
        rb_raise (rb_eArgError, "wrong number of argument %d (expected 3)", RARRAY_LEN(val_bias));
    }
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    VertexArray* positions;
    Data_Get_Struct (val_positions, VertexArray, positions);
    float scale = NUM2DBL(val_scale); 
    float bias[3];
    bias[0] = NUM2DBL(rb_ary_entry(val_bias, 0));
    bias[1] = NUM2DBL(rb_ary_entry(val_bias, 1));
    bias[2] = NUM2DBL(rb_ary_entry(val_bias, 2));
    __TRY__;
    p->setPositions (positions, scale, bias);
    __CATCH__;
    return Qnil;
}

VALUE ruby_VertexBuffer_print (VALUE self)
{
    VertexBuffer* p;
    Data_Get_Struct (self, VertexBuffer, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 *  VertexBuffer:: TexCoordsAccessor
 */
VALUE ruby_VertexBuffer_TexCoordsAccessor_get (VALUE self, VALUE val_index)
{
    TexCoordsAccessor* p;
    Data_Get_Struct (self, TexCoordsAccessor, p);
    int          index = NUM2INT(val_index);
    float        scale_bias[4] = {0,0,0,0};
    VertexArray* tex_coords;
    __TRY__;
    tex_coords = p->vertex_buffer->getTexCoords (index, scale_bias);
    __CATCH__;
    VALUE val_tex_coords = tex_coords ? (VALUE)tex_coords->getExportedEntity() : Qnil;
    VALUE val_scale      = rb_float_new(scale_bias[0]);
    VALUE val_bias       = rb_ary_new2 (3);
    rb_ary_push (val_bias, rb_float_new(scale_bias[1]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[2]));
    rb_ary_push (val_bias, rb_float_new(scale_bias[3]));
    VALUE val_ret = rb_ary_new2 (3);
    rb_ary_push (val_ret, val_tex_coords);
    rb_ary_push (val_ret, val_scale);
    rb_ary_push (val_ret, val_bias);
    return val_ret;
}

VALUE ruby_VertexBuffer_TexCoordsAccessor_set (VALUE self, VALUE val_index, VALUE val_args)
{
    rb_check_type (val_args, T_ARRAY);
    if (RARRAY_LEN(val_args) != 3) {
        rb_raise (rb_eArgError, "wrong array length %d (expected 3)", RARRAY_LEN(val_args));
    }
    VALUE val_tex_coords = rb_ary_entry (val_args, 0);
    VALUE val_scale      = rb_ary_entry (val_args, 1);
    VALUE val_bias       = rb_ary_entry (val_args, 2);
    if (!rb_obj_is_kind_of (val_tex_coords, rb_cVertexArray)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexArray)");
    }
    if (RARRAY_LEN(val_bias) < 2) {
        rb_raise (rb_eArgError, "wrong array length %d (expected 2)", RARRAY_LEN(val_bias));
    }
    TexCoordsAccessor* p;
    Data_Get_Struct (self, TexCoordsAccessor, p);
    VertexArray* tex_coords;
    Data_Get_Struct (val_tex_coords, VertexArray, tex_coords);
    int          index   = NUM2INT(val_index);
    float        scale   = NUM2DBL(val_scale); 
    float        bias[3] = {0,0,0};
    bias[0] = NUM2DBL(rb_ary_entry(val_bias, 0));
    bias[1] = NUM2DBL(rb_ary_entry(val_bias, 1));
    __TRY__;
    p->vertex_buffer->setTexCoords (index, tex_coords, scale, bias);
    __CATCH__;
    return Qnil;


}

/**
 * VertexBufferクラスのRubyへの登録
 */
void register_VertexBuffer ()
{
    // VertexBuffer
    rb_cVertexBuffer        = rb_define_class_under (rb_mM3G, "VertexBuffer",        rb_cObject3D);

    rb_define_alloc_func     (rb_cVertexBuffer, ruby_VertexBuffer_allocate);
    rb_define_private_method (rb_cVertexBuffer, "initialize", (VALUE(*)(...))ruby_VertexBuffer_initialize, 0);

    rb_define_method (rb_cVertexBuffer, "colors",         (VALUE(*)(...))ruby_VertexBuffer_get_colors,        0);
    rb_define_method (rb_cVertexBuffer, "default_color",  (VALUE(*)(...))ruby_VertexBuffer_get_default_color, 0);
    rb_define_method (rb_cVertexBuffer, "normals",        (VALUE(*)(...))ruby_VertexBuffer_get_normals,       0);
    rb_define_method (rb_cVertexBuffer, "positions",      (VALUE(*)(...))ruby_VertexBuffer_get_positions,     0);
    rb_define_method (rb_cVertexBuffer, "tex_coords",     (VALUE(*)(...))ruby_VertexBuffer_get_tex_coords,    0);
    rb_define_method (rb_cVertexBuffer, "vertex_count",   (VALUE(*)(...))ruby_VertexBuffer_get_vertex_count,  0);
    rb_define_method (rb_cVertexBuffer, "colors=",        (VALUE(*)(...))ruby_VertexBuffer_set_colors,        1);
    rb_define_method (rb_cVertexBuffer, "default_color=", (VALUE(*)(...))ruby_VertexBuffer_set_default_color, 1);
    rb_define_method (rb_cVertexBuffer, "normals=",       (VALUE(*)(...))ruby_VertexBuffer_set_normals,       1);
    rb_define_method (rb_cVertexBuffer, "positions=",     (VALUE(*)(...))ruby_VertexBuffer_set_positions,     1);
    rb_define_method (rb_cVertexBuffer, "to_s",           (VALUE(*)(...))ruby_VertexBuffer_print,             0);

    // VertexBuffer:: TexCoordsAccessor
    rb_cVertexBuffer_TexCoordsAccessor  = rb_define_class_under (rb_cVertexBuffer, "TexCoordsAccessor", rb_cObject);

    rb_define_method (rb_cVertexBuffer_TexCoordsAccessor, "[]",    (VALUE(*)(...))ruby_VertexBuffer_TexCoordsAccessor_get,   1);
    rb_define_method (rb_cVertexBuffer_TexCoordsAccessor, "[]=",   (VALUE(*)(...))ruby_VertexBuffer_TexCoordsAccessor_set,   2);
}
